package eval.deploy;

import java.util.ArrayList;


import cern.jet.random.AbstractDistribution;
import cern.jet.random.Normal;
import cern.jet.random.Poisson;

import com.google.inject.Inject;

import eval.exceptions.NotImplementedExecption;
//import eval.exceptions.NotImplementedExecption;
import eval.utilities.configuration.ExperimentConfiguration;
import eval.utilities.random.ProbabilisticDistributionsEnum;
import eval.utilities.random.RandomUtilities;
import eval.vm.IVMFactory;

/**
 * Initializes the Deploy Manager
 * */
public class DeployGenerator implements IDeployGenerator {

	private static ExperimentConfiguration configuration = ExperimentConfiguration
			.getSingleton();

	private IDeployManager deployManager;
	private IVMFactory vmFactory;
	private AbstractDistribution newDeployDistribution = null;
	private IDemandManager demandManager;
	private int  negativeNumberOfRequestedVMs = 0;
	private int  negativeLifeTime = 0;

	@Inject
	public DeployGenerator(IDeployManager deployManager, IVMFactory vmFactory, IDemandManager demandManager) {
		this.deployManager = deployManager;
		this.vmFactory = vmFactory;
		this.demandManager = demandManager;
	}

	@Override
	public void generateDeploys() {
		//we assume that we start the exp. with vm to deploy
		generateInitialCycleDeploy();
		generateOtherCyclesDeploys();
	}

	// generates the initial deploy set
	private void generateInitialCycleDeploy() {
		DeploySet ds = new DeploySet(vmFactory,extractDeployLifeTime(configuration.numberOfInitialVMs));
		deployManager.setDeploySetAtTick(ds, 0);
	}

	private void generateOtherCyclesDeploys() {

		int nextDeployTick = extractNewDeployCycle();

		for (int tick = 1; tick <configuration.experimentTicks; tick++) {
			DeploySet ds = new DeploySet();

			if (nextDeployTick == tick) {
				ds = createNewDeploySet();
				nextDeployTick = tick + Math.max(1,extractNewDeployCycle());
			}
			//TODO
			//demandManager.synchronizeVMs(ds);
			deployManager.setDeploySetAtTick(ds, tick);
		}

	}

	private DeploySet createNewDeploySet() {
		int extractNumberOfVMsToDeploy = extractNumberOfVMsToDeploy();
		ArrayList<Integer> deployLifetime = extractDeployLifeTime(extractNumberOfVMsToDeploy);
		return new DeploySet(vmFactory, deployLifetime);
	}

	private int extractNewDeployCycle() {
		switch (ProbabilisticDistributionsEnum
				.valueOf(configuration.averageTimeBetweenDeployRequestsDistribution)) {

				case NORMAL: 
					if (this.newDeployDistribution == null) {
						newDeployDistribution = RandomUtilities
								.getNewNormalDistribution(configuration.averageTimeBetweenDeployRequests,configuration.averageTimeBetweenDeployRequests*0.2);
					}
					return newDeployDistribution.nextInt();

				case POISSON:
					if (this.newDeployDistribution == null) {
						newDeployDistribution = RandomUtilities
								.getNewPoissonDistribution(configuration.averageTimeBetweenDeployRequests);
					}
					return newDeployDistribution.nextInt();

				case CONSTANT:
					return configuration.averageTimeBetweenDeployRequests;

				default:
					throw new IllegalArgumentException(
							"The requested distribution for deploy is not implemented");
		}
	}

	private ArrayList<Integer> extractDeployLifeTime(int numberOfVMsToDeploy) {

		ArrayList<Integer> deployLifeTimeList = new ArrayList<Integer>();
		int average = configuration.deploySetAverageDuration;
		switch (ProbabilisticDistributionsEnum
				.valueOf(configuration.deploySetsDurationDistribution)) {
				case POISSON:
					for (int i=0;i<numberOfVMsToDeploy;i++) {
						Poisson distribution = RandomUtilities
								.getNewPoissonDistribution(average);
						deployLifeTimeList.add(new Integer(distribution.nextInt() + 1));
					}
					return deployLifeTimeList;
				case CONSTANT:
					for (int i=0;i<numberOfVMsToDeploy;i++) {
						deployLifeTimeList.add( new Integer(average));
					}
					return deployLifeTimeList;
				case NORMAL:
					for (int i=0;i<numberOfVMsToDeploy;i++) {
						Normal distribution = RandomUtilities.getNewNormalDistribution(average, 
								average * configuration.deploySetAverageDurationSTD);
						deployLifeTimeList.add( new Integer(distribution.nextInt() + 1));
						//deployLifeTimeList.add( new Integer(makeSureReturnValueNotNegativeTemp(distribution.nextInt())));
					}
					return deployLifeTimeList;
				default:
					throw new NotImplementedExecption(
							configuration.deploySetsDurationDistribution
							+ " is not implemented in the set of know distributions");
		}
	}

	private int extractNumberOfVMsToDeploy() {
		switch (ProbabilisticDistributionsEnum
				.valueOf(configuration.averageNumberOfRequestedVMsDistribution)) {
				case NORMAL:
					Normal tmp = RandomUtilities.
					getNewNormalDistribution(configuration.averageNumberOfRequestedVMs, configuration.averageNumberOfRequestedVMs * configuration.averageNumberOfRequestedVMsSTD);
					return makeSureReturnValueNotNegative(tmp.nextInt());
				case POISSON:
					Poisson distribution = RandomUtilities
					.getNewPoissonDistribution(configuration.averageNumberOfRequestedVMs);
					return  makeSureReturnValueNotNegative(distribution.nextInt());

				case CONSTANT:
					return  makeSureReturnValueNotNegative(configuration.averageNumberOfRequestedVMs);

				default:
					throw new IllegalArgumentException(
							"The requested distribution for deploy is not implemented");
		}

	}
	
	private int makeSureReturnValueNotNegative(int averageNumberOfRequestedVMs){
		if(averageNumberOfRequestedVMs < 0){
			this.negativeNumberOfRequestedVMs += averageNumberOfRequestedVMs;
			return 0;
		} else {
			averageNumberOfRequestedVMs += this.negativeNumberOfRequestedVMs;
			if(averageNumberOfRequestedVMs < 0){
				this.negativeNumberOfRequestedVMs = averageNumberOfRequestedVMs;
				return 0;
			} else{
				this.negativeNumberOfRequestedVMs = 0;
				return averageNumberOfRequestedVMs;
			}
		}
		
	
	}
	
	private int makeSureReturnValueNotNegativeTemp(int lifeTime){
		if(lifeTime < 0){
			this.negativeLifeTime += lifeTime;
			return 0;
		}
		else{
			lifeTime += this.negativeLifeTime;
			if(lifeTime < 0){
				this.negativeLifeTime = lifeTime;
				return 0;
			}
			else{
				this.negativeLifeTime = 0;
				return lifeTime;
			}
		}
		
	
	}
}

